package org.robot.scm.platform.server.handler;

import static org.robot.scm.platform.server.LockKey.EXPORT_KEY;

import com.sun.net.httpserver.HttpExchange;
import com.sun.net.httpserver.HttpHandler;
import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.sql.Connection;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.ibatis.jdbc.SqlRunner;
import org.robot.scm.infra.RedisCacheHelper;
import org.robot.scm.platform.registry.HikariRegistry;
import org.robot.scm.pojos.enums.PlatformEnum;
import org.robot.scm.pojos.platform.server.ExecuteParam;
import org.robot.scm.pojos.platform.server.SshMySql;
import org.robot.scm.pojos.platform.sqlite.ExpFile;
import org.robot.scm.utils.DateTool;
import org.robot.scm.utils.JsonHelper;
import org.robot.scm.utils.SQLiteUtil;

/**
 * 导出文件列表
 */
@Slf4j
@NoArgsConstructor
public class ExpFileHandler extends BaseHandler implements HttpHandler {

    @Override
    public String getUri() {
        return "/expFile";
    }

    @Override
    public void handle(HttpExchange exchange) throws IOException {
        // 并发校验
        String locker = RedisCacheHelper.get(EXPORT_KEY);
        if (StringUtils.isNotBlank(locker)) {
            sendSuccessResponse(exchange, "有导出任务执行中");
            return;
        }

        if (!exchange.getRequestMethod().equalsIgnoreCase("POST")) {
            sendFailureResponse(exchange, "请求非法");
            return;
        }
        String requestBody = new BufferedReader(new InputStreamReader(exchange.getRequestBody(), StandardCharsets.UTF_8)).lines().collect(Collectors.joining("\n"));
        log.info("export param: {}", requestBody);
        ExecuteParam executeParam = JsonHelper.toObject(requestBody, ExecuteParam.class);

        String fileKey = DateTool.format(new Date(), "yyyyMMddHHmmss");
        File targetFile = new File("assets/telegram/" + fileKey + ".txt");

        RedisCacheHelper.put(EXPORT_KEY, fileKey);

        List<String> params = executeParam.initParams();
        // 写入文件
        String trimmed = executeParam.getScript().trim();
        String handledSql = trimmed.endsWith(";") ? trimmed.substring(0, trimmed.length() - 1) : trimmed;
        ExpFile file = ExpFile.builder()
                .fileName(targetFile.getName())
                .originSql(handledSql)
                .progress(0)
                .remark("")
                .expEnv(params.get(0))
                .build();
        SQLiteUtil.insert(file);

        SshMySql sshMysql = config(PlatformEnum.SQL).getCurrentPlatform().getSshMysql(file.getExpEnv());
        try (Connection connection = HikariRegistry.getHikariMap().get(sshMysql.getName()).getConnection(); OutputStream out = Files.newOutputStream(targetFile.toPath())) {
            SqlRunner runner = new SqlRunner(connection);

            // count
            int pageSize = 5000;
            String countSql = "SELECT count(*) as totalNumber FROM (" + file.getOriginSql() + ") a";

            Map<String, Object> selectOne = runner.selectOne(countSql);
            log.info("count sql: {}, result: {}", countSql, JsonHelper.toJSONString(selectOne));
            int totalNumber = Optional.ofNullable((Long) selectOne.get("TOTALNUMBER")).map(Long::intValue).orElse(0);
            if (totalNumber == 0) {
                file.setRemark("size=0");
                file.setProgress(100);
                SQLiteUtil.update(file, ExpFile.builder().fileName(file.getFileName()).build());
                return;
            }
            if (totalNumber <= pageSize) {
                List<Map<String, Object>> maps = runner.selectAll(file.getOriginSql());
                writeFile(file, out, totalNumber, maps, 100);
                return;
            }

            long totalPages = (long) Math.ceil((double) totalNumber / pageSize);
            for (int cur = 1; cur <= totalPages; cur++) {
                String listSql = "select * from (" + file.getOriginSql() + ") a limit " + (cur - 1) * pageSize + ", " + pageSize;
                log.info("list page: {}, sql: {}", cur, listSql);
                List<Map<String, Object>> maps = runner.selectAll(listSql);
                int progress = (int) Math.ceil((double) cur / totalPages * 100);
                writeFile(file, out, totalNumber, maps, progress);
            }
        } catch (Exception e) {
            SQLiteUtil.delete(file);
            targetFile.delete();
            log.error("error occur", e);
            throw new RuntimeException(e);
        } finally {
            RedisCacheHelper.remove(EXPORT_KEY);
        }
    }

    private void writeFile(ExpFile expFile, OutputStream out, int totalNumber, List<Map<String, Object>> maps, int process) {
        try {
            Set<String> titles = maps.get(0).keySet();
            out.write((titles + "\n").getBytes(StandardCharsets.UTF_8));
            for (Map<String, Object> map : maps) {
                out.write((map.values() + "\n").getBytes(StandardCharsets.UTF_8));
            }
            out.flush();
            expFile.setRemark("size=" + totalNumber);
        } catch (IOException e) {
            expFile.setRemark("size=" + totalNumber + ", error: " + e.getMessage());
        } finally {
            expFile.setProgress(process);
            SQLiteUtil.update(expFile, ExpFile.builder().fileName(expFile.getFileName()).build());
        }
    }

}
